#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
##############################################################################
# Descrição:
# Esse é um pequeno programa para editar mapas de tiles.
#
# Autor: Kao Cardoso Félix
#
# Dependências:
#   - python >= 2.5
#   - pygame (http://www.pygame.org)
#   - pgu (http://www.imitationpickles.org/pgu/wiki/index)
##############################################################################

import math
import pickle

import pygame
from pygame.locals import *

from pgu import gui
        
from tiles import TileSet, TileLayer, test_map
from dialogs import *

class ActionDraw(object):
    def __init__(self, map):
        self.map = map
        self.ended = False
        self.old_state = {}
        
    def do(self, pos0, pos1, value):
        """Draw a line using Bresenham algorythm and keeps track
        of drawing history"""
        
        x0, y0 = pos0
        x1, y1 = pos1
        
        dx = x1 - x0
        dy = y1 - y0
        stepx, stepy = 1, 1

        if dy < 0: 
            dy = -dy  
            stepy = -1
        if dx < 0: 
            dx = -dx
            stepx = -1
        dy = dy << 1
        dx = dx << 1

        if (x0, y0) not in self.old_state :self.old_state[(x0, y0)] = self.map[y0][x0]
        self.map[y0][x0] = value
        if dx > dy:
            fraction = dy - (dx >> 1)
            while x0 != x1:
                if fraction >= 0:
                    y0 += stepy
                    fraction -= dx
                    
                x0 += stepx
                fraction += dy
                if (x0, y0) not in self.old_state :self.old_state[(x0, y0)] = self.map[y0][x0]
                self.map[y0][x0] = value
        else:
            fraction = dx - (dy >> 1)
            while y0 != y1:
                if fraction >= 0:
                    x0 += stepx
                    fraction -= dy
                y0 += stepy
                fraction += dx
                if (x0, y0) not in self.old_state :self.old_state[(x0, y0)] = self.map[y0][x0]
                self.map[y0][x0] = value
                
    def undo(self):
        #print self.old_state
        for pos, value in self.old_state.items():
            self.map[pos[1]][pos[0]] = value
        
        
class TileWindow(gui.Widget):
    """ TileWindow
    A window that shows tiles on a tile set and let the user select 
    them to paint the map
    """
    def __init__(self, tile_set, **params):
        self.cols = 5
        
        self.cursor_pos = (0, 0)
        self.tile_index = 0
        self.tile_set = tile_set
        
        w, h = (self.cols*self.tile_set.tile_size, int(math.ceil(len(tile_set)/float(self.cols)))*self.tile_set.tile_size)
        gui.Widget.__init__(self, width=w, height=h, **params)
        
        self.surface = pygame.Surface((w, h), pygame.SRCALPHA,32)
        
        # load tiles on surface
        pos_x = 0
        pos_y = 0
        for tile in self.tile_set:
            self.surface.blit(tile, (pos_x*self.tile_set.tile_size, pos_y*self.tile_set.tile_size))
            pos_x += 1
            if pos_x > self.cols-1:
                pos_x = 0
                pos_y += 1
        
        self.rect = self.surface.get_rect()
        
        # create selection cursor
        self.cursor = pygame.image.load('cursor.png')        
        self.repaint()

    def paint(self,s):
        s.blit(self.surface,(0,0))
        s.blit(self.cursor, self.cursor_pos)
        
    def event(self,e):
        if e.type is gui.MOUSEBUTTONUP:
            self.cursor_pos = ((e.pos[0] / self.tile_set.tile_size)*self.tile_set.tile_size, (e.pos[1] / self.tile_set.tile_size)*self.tile_set.tile_size)
            self.tile_index = (e.pos[0] / self.tile_set.tile_size) + (e.pos[1] / self.tile_set.tile_size)*self.cols
            
            self.repaint()
            
            print "Tile index ", self.tile_index

class MapWindow(gui.ScrollArea):
    def __init__(self, map, tile_window = None, width=0, height=0, hscrollbar=True, vscrollbar=True,step=24, **params):
        params.setdefault('cls', 'scrollarea')
        gui.Table.__init__(self, width=width,height=height,**params)
        
        h, w = (len(map) * tile_window.tile_set.tile_size , len(map[0]) * tile_window.tile_set.tile_size)
        
        self.sbox = MapArea(map, (width,height), tile_window)
        self.widget = gui.Widget(width=w, height=h)
        
        self.vscrollbar = vscrollbar
        self.hscrollbar = hscrollbar
        
        self.step = step
        
        self.undo = self.sbox.undo

    def load_map(self, map):
        h, w = (len(map) * self.sbox.tile_window.tile_set.tile_size , len(map[0]) * self.sbox.tile_window.tile_set.tile_size)
        self.widget.style.width = w
        self.widget.style.height = h
        self.sbox.load_map(map)
        self.resize()
        
class MapArea(gui.Container):
    def __init__(self, map, surf_dim, tile_window = None, **params):
        self.drawing = False
        self.last_draw_pos = None
        
        self.action = None
        self.undo_history = []
        #self.redo_history = []
        
        self.tile_window = tile_window
        
        self.h, self.w = (len(map) * self.tile_window.tile_set.tile_size , len(map[0]) * self.tile_window.tile_set.tile_size)
        gui.Container.__init__(self, width=surf_dim[0], height=surf_dim[1], **params)
        
        self.map = map
        self.surface = pygame.Surface(surf_dim)
        
        self.offset = [0, 0]
        
        self.repaint()
    
    def load_map(self, map):
        self.map = map
        self.repaint()
        
    def resize_map(self, w, h):
        # TODO: Implement this method correctly
        self.surface = self.surface.subsurface((0,0,w*cell_size,h*cell_size))
    
    def paint(self,s):
        pos = (self.offset[0], self.offset[1])
        self._draw_map(s, pos)
        gui.Container.paint(self, s)
      
    def update(self, s):
        ret = [s.get_rect()]
        self.paint(s)
        return ret
        
    def draw_down(self,e):
        self.drawing = True
        self.last_draw_pos = e.pos
        self.action = ActionDraw(self.map)
        self.draw_motion(e)
        
    def draw_motion(self,e):
        if not self.drawing: return
        if self.tile_window:
            ts = self.tile_window.tile_set.tile_size
            
            pos0 = (self.last_draw_pos[0] + self.offset[0], self.last_draw_pos[1] + self.offset[1])
            pos1 = (e.pos[0] + self.offset[0], e.pos[1] + self.offset[1])
            
            map_pos0 = ((pos0[0] / ts), (pos0[1] / ts))
            map_pos1 = ((pos1[0] / ts), (pos1[1] / ts))
            #blit_pos = (map_x*ts, map_y*ts)
            
            self.action.do(map_pos0, map_pos1, self.tile_window.tile_index)
            self.last_draw_pos = e.pos
            #self.map[map_y][map_x] = self.tile_window.tile_index
            
        self.repaint()
        
    def draw_up(self,e):
        self.undo_history.append(self.action)
        self.drawing = False

    def undo(self):
        if self.undo_history:
            action = self.undo_history.pop()
            action.undo()
            self.repaint()

    def event(self,e):
        if not self.surface: return
        
        if e.type == gui.MOUSEBUTTONDOWN:
            if hasattr(self,app.mode.value+"_down"):
                action = getattr(self,app.mode.value+"_down")
                action(e)
        if e.type == gui.MOUSEMOTION:
            if hasattr(self,app.mode.value+"_motion"):
                action = getattr(self,app.mode.value+"_motion")
                action(e)
        if e.type is gui.MOUSEBUTTONUP:
            if hasattr(self,app.mode.value+"_up"):
                action = getattr(self,app.mode.value+"_up")
                action(e)

    def _draw_map(self, surf, pos):
        rect = Rect(pos[0], pos[1], surf.get_width(), surf.get_height())
        
        x1, y1 = rect.topleft
        x2, y2 = rect.bottomright
        
        x1 = int(math.floor(float(x1)/self.tile_window.tile_set.tile_size))
        y1 = int(math.floor(float(y1)/self.tile_window.tile_set.tile_size))
        
        x2 = int( min(math.ceil(float(x2)/self.tile_window.tile_set.tile_size), len(self.map[0])) )
        y2 = int( min(math.ceil(float(y2)/self.tile_window.tile_set.tile_size), len(self.map[0])) )

        for t_y in range(y1, y2):
            for t_x in range(x1, x2):
                if self.map[t_y][t_x] >= 0:
                    t = self.tile_window.tile_set[self.map[t_y][t_x]]
                    l = (t_x*self.tile_window.tile_set.tile_size-pos[0], t_y*self.tile_window.tile_set.tile_size-pos[1])
                    surf.blit(t, l)


class App(gui.Desktop):
    def __init__(self,**params):
        gui.Desktop.__init__(self,**params)
        
        ## Logic and Data
        tile_set = TileSet('tiles.png', 2) # Load a tileset for the TileWindow and the MapWindow
        self.map_file_name = 'untitled.map'
        
        
        ## Gui Creation
        self.connect(gui.QUIT,self.quit,None)
        
        c = gui.Container(width=800,height=600)
        spacer = 8
        
        # Initializing the Dialogs
        self.open_d = gui.FileDialog("Open Map")
        self.open_d.connect(gui.CHANGE,self.action_open,None)
        self.save_d = gui.FileDialog("Save Map")
        self.save_d.connect(gui.CHANGE,self.action_saveas,None)
        self.quit_d = QuitDialog()
        self.quit_d.connect(QUIT,self.quit,None)
        
        # Initializing the Menus
        menus = gui.Menus([
            #('File/New',self.new_d.open,None),
            ('File/New (Ctrl+N)',None,None),
            ('File/Open (Ctrl+O)',self.open_d.open,None),
            ('File/Save (Ctrl+S)',self.action_save,self.map_file_name),
            ('File/Save As...',self.save_d.open,None),
            ('File/Quit (Ctrl+W)',self.quit,None),
            
            ('Edit/Undo (Ctrl+Z)',self.action_undo,None),
            ('Edit/Redo (Ctrl+Y)',None,None),
            
            #~ ('Help/Help',None,None),
            #~ ('Help/About',None,None),
            ])
        c.add(menus,0,0)
        menus.rect.w,menus.rect.h = menus.resize()
        #print 'menus',menus.rect
        
        # Initializing the Toolbox
        self.mode = mode = gui.Toolbox([
            ('draw','draw'),
            ('erase','box'),
            ('select','select'),
            ],value='draw')
            
        c.add(mode, spacer, menus.rect.bottom+spacer)
        mode.rect.x,mode.rect.y = mode.style.x,mode.style.y
        mode.rect.w,mode.rect.h = mode.resize()
        #mode._resize()

        # Add a tile window with scroll bars
        self.tile_window = TileWindow(tile_set)
        tw_scroll_area = gui.ScrollArea(self.tile_window, 178, 400, hscrollbar=False,style={'border':1})
        c.add(tw_scroll_area, spacer, mode.rect.bottom + spacer)
        
        # Add a map window with scroll_bars
        w = c.rect.w - tw_scroll_area.rect.w - spacer*3 - 5
        h = 400
        
        self.map_window = MapWindow(test_map, self.tile_window, w, h, style={'border':1})        
        c.add(self.map_window, tw_scroll_area.rect.right + 10 + spacer, mode.rect.bottom + spacer)
        
        self.widget = c
    
    def event(self,e):
        if e.type == gui.KEYDOWN:
            if e.mod & (KMOD_LCTRL | KMOD_RCTRL):
                if e.key == K_n: # Ctrl + N
                    pass
                elif e.key == K_o: # Ctrl + O
                    pass
                elif e.key == K_s: # Ctrl + S
                    pass
                elif e.key == K_w: # Ctrl + W
                    pass
                elif e.key == K_y: # Ctrl + Y
                    print "Redo"
                elif e.key == K_z: # Ctrl + Z
                    print "Undo"
                    self.map_window.undo()
            
            elif e.key == K_F5:
                self.action_refresh_map()
                    
        gui.Desktop.event(self, e)
        
    
    def action_refresh_map(self):
        self.map_window.repaint()
    
    def action_new(self,value):
        pass
        #~ self.new_d.close()
        #~ self.fname = 'untitled.tga'
        #~ self.painter.init(self.new_d.value.results())
        
    def action_save(self,file_name):
        map_file = file(file_name, 'w')
        pickle.dump(self.map_window.sbox.map, map_file)
        
    def action_saveas(self,value):
        self.save_d.close()
        self.map_file_name = self.save_d.value
        self.action_save(self.map_file_name)
        
    def action_open(self,value):
        self.open_d.close()
        self.map_file_name = self.open_d.value
        print self.map_file_name
        
        try:
            map_file = file(self.map_file_name)
            map_data = pickle.load(map_file)
            
            self.map_window.load_map(map_data)
        except IOError:
            print "File not found"
    
    def action_undo(self, value):
        self.map_window.undo()

if __name__ == '__main__':
    app = App(theme=gui.Theme(['default','./data/themes/tools']))
    app.run()
